package rabbit.flt.plugins.common;

import rabbit.flt.common.Metrics;
import rabbit.flt.common.context.TraceContext;
import rabbit.flt.common.log.AgentLoggerFactory;
import rabbit.flt.common.log.Logger;
import rabbit.flt.common.trace.MethodStackInfo;
import rabbit.flt.common.trace.TraceData;
import rabbit.flt.common.trace.io.HttpRequest;
import rabbit.flt.common.trace.io.HttpResponse;
import rabbit.flt.common.utils.PathParser;
import rabbit.flt.common.utils.ResourceUtils;
import rabbit.flt.common.utils.StringUtils;

import java.io.BufferedReader;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;

public abstract class AbstractPlugin implements Plugin {

    protected Logger logger = AgentLoggerFactory.getLogger(getClass());

    /**
     * trace数据处理器
     */
    protected static final DefaultTraceDataHandler dataHandler = new DefaultTraceDataHandler();

    /**
     * 方法前置增强
     *
     * @param objectEnhanced
     * @param method
     * @param args
     * @return 返回入参
     * @throws Exception
     */
    @Override
    public Object[] before(Object objectEnhanced, Method method, Object[] args) {
        TraceContext.pushStack(method);
        return args;
    }

    /**
     * 后置拦截
     *
     * @param objectEnhanced
     * @param method
     * @param args
     * @param result
     * @return
     */
    @Override
    public Object after(Object objectEnhanced, Method method, Object[] args, Object result) {
        return result;
    }

    /**
     * final增强
     *
     * @param objectEnhanced
     * @param method
     * @param args
     * @param result
     */
    @Override
    public void doFinal(Object objectEnhanced, Method method, Object[] args, Object result) {
        MethodStackInfo stackInfo = TraceContext.popStack(method);
        if (null == stackInfo) {
            return;
        }
        if (stackInfo.isPopped()) {
            TraceData traceData = stackInfo.getTraceData();
            fillTraceData(traceData, objectEnhanced, method, args, result);
            handleTraceData(traceData);
        }
    }

    /**
     * 异常兜底
     *
     * @param objectEnhanced
     * @param method
     * @param args
     * @param t
     */
    @Override
    public void onException(Object objectEnhanced, Method method, Object[] args, Throwable t) {
        MethodStackInfo stackInfo = getStackInfo(method);
        if (null == stackInfo) {
            return;
        }
        TraceData traceData = stackInfo.getTraceData();
        traceData.setStatus(TraceData.Status.ERR);
        TraceContext.getTraceMetaData().trySaveErrorMethod(method);
        if (TraceContext.getTraceMetaData().isErrorMethod(method)) {
            traceData.setData(getStackText(t));
        }
    }

    protected MethodStackInfo getStackInfo(Method method) {
        return TraceContext.getStackInfo(method);
    }

    /**
     * 读取堆栈文本
     *
     * @param t
     * @return
     */
    protected String getStackText(Throwable t) {
        StringWriter writer = new StringWriter();
        PrintWriter pw = new PrintWriter(writer);
        t.printStackTrace(pw);
        String text = writer.toString();
        BufferedReader br = new BufferedReader(new StringReader(text));
        StringBuilder sb = new StringBuilder();
        int lineCount = 0;
        while (true) {
            try {
                String line = br.readLine();
                sb.append(null == line ? "" : line).append("\r");
                if (null == line || 30 == ++lineCount) {
                    break;
                }
            } catch (Exception e) {
                // ignore
            }
        }
        ResourceUtils.close(writer);
        ResourceUtils.close(pw);
        ResourceUtils.close(br);
        return sb.toString();
    }

    /**
     * 处理trace数据
     *
     * @param traceData
     */
    protected void handleTraceData(TraceData traceData) {
        if (!traceData.isValidTrace()) {
            // 无效trace直接丢弃
            return;
        }
        dataHandler.process(traceData);
    }

    /**
     * 填充trace数据
     *
     * @param traceData
     * @param objectEnhanced
     * @param method
     * @param args
     * @param result
     */
    protected void fillTraceData(TraceData traceData, Object objectEnhanced, Method method, Object[] args, Object result) {
        // do nothing
    }

    protected boolean isTraceOpened() {
        return TraceContext.isTraceOpened();
    }

    /**
     * 截断数据
     *
     * @param txt
     * @return
     */
    protected String truncate(String txt) {
        int maxLength = 1024;
        if (!StringUtils.isEmpty(txt) && txt.length() > maxLength) {
            return txt.substring(0, maxLength) + ".....";
        }
        return txt;
    }

    /**
     * 补偿设置节点名字
     *
     * @param traceData
     */
    protected final void setNodeNameWithMappedController(TraceData traceData) {
        if (traceData.isHasMappedController()) {
            return;
        }
        HttpRequest httpRequest = traceData.getHttpRequest();
        String contextPath = httpRequest.getContextPath();
        contextPath = contextPath.equals("/") ? "" : contextPath;
        Set<Map.Entry<Class, List<String>>> entries = ControllerPathHelper.getClassPathCache().entrySet();
        for (Map.Entry<Class, List<String>> entry : entries) {
            for (String path : entry.getValue()) {
                String definedPath = contextPath + path;
                if (PathParser.parsePattern(definedPath).match(httpRequest.getRequestUri())) {
                    traceData.setNodeName(definedPath);
                    traceData.setHasMappedController(true);
                    return;
                }
            }
        }
    }

    /**
     * 根据sql获取操作名字
     *
     * @param sql
     * @return
     */
    protected String getNameBySql(String sql) {
        if (sql.toLowerCase().startsWith("select")) {
            String sub = sql.toLowerCase().substring("select".length()).trim();
            if (sub.startsWith("count(")) {
                return "COUNT";
            }
            return "SELECT";
        } else if (sql.toLowerCase().startsWith("update")) {
            return "UPDATE";
        } else if (sql.toLowerCase().startsWith("insert")) {
            return "INSERT";
        } else if (sql.toLowerCase().startsWith("delete")) {
            return "DELETE";
        } else {
            return "UNKNOWN";
        }
    }

    /**
     * 创建trace数据
     *
     * @param method
     * @return
     */
    protected TraceData createTraceData(Method method) {
        String rootSpanId = TraceContext.getRootSpanId();
        AtomicLong spanIdChildCounter = TraceContext.getSpanIdChildCounter(rootSpanId);
        String spanId = rootSpanId.concat("-").concat(Long.toString(spanIdChildCounter.getAndAdd(1L)));
        TraceData traceData = new TraceData();
        traceData.setIndex(TraceContext.incrementNode());
        traceData.setTargetMethod(method);
        traceData.setTraceId(TraceContext.getTraceId());
        traceData.setSpanId(spanId);
        traceData.setNodeDesc(method.getDeclaringClass().getName() + "." + method.getName());
        traceData.setRequestTime(System.currentTimeMillis());
        traceData.setServerIp(Metrics.getHostIp());
        traceData.setStatus(TraceData.Status.OK);
        traceData.setHttpResponse(new HttpResponse());
        return traceData;
    }
}
